<script setup lang="jsx">
import { differenceBy, uniqBy, isEqual } from 'lodash'
import { request, getSizeByDir } from '@renderer/utils'
import { get, add, set, del, getList } from '../../hooks/useDb'
import { shell } from 'electron'
import { useConfirm } from 'primevue/useconfirm'
import { useToast } from 'primevue/usetoast'
import moment from 'moment'

let num = 0

const toast = useToast()
const confirm = useConfirm()
const fs = require('node:fs')
let sortOptions = ref([
  {
    name: '打开时间',
    direction: false,
    fn: (a, b) => (b.lastTime ?? 0) - (a.lastTime ?? 0)
  },
  {
    name: '添加时间',
    direction: false,
    fn: (a, b) => b.createTime - a.createTime
  },
  {
    name: '游戏名称',
    direction: false,
    fn: (a, b) => (a.showName || a.name).localeCompare(b.showName || b.name)
  },
  {
    name: '游戏大小',
    direction: false,
    fn: (a, b) => b.size - a.size
  }
])
let zIndex = ref(100) // 遮罩层级
let libraryList = ref([]) // 游戏库目录
let allGame = ref([]) // 游戏列表
let active = ref(-1) // 当前活动的游戏索引
let data = ref({}) // 下拉编辑数据
let apps = ref([]) // 左上角自定义快捷应用
let keyword = ref('') // 搜索关键字
let extraInfo = ref(0)
let sort = ref(sortOptions.value[0])
const ActionBtn = <ripple-button nd rounded class="mr-3" />
const SmallBtn = <ripple-button class="flex-fill pa-1" style="font-size: 0.7rem" raised rounded />

watch(active, (v) => {
  window.app.toggleAttribute('mask')
  v == -1 && (extraInfo.value = 0)
})
const showGames = computed(() => [...allGame.value].filter((_) => !_.hide))
const gameList = computed(() => {
  let list = showGames.value.filter((_) => _.name?.toLowerCase().includes(keyword.value.toLowerCase()) || _.showName?.toLowerCase().includes(keyword.value.toLowerCase()))
  list.sort(sort.value.fn)
  sort.value.direction && list.reverse()
  return list
})

const getAllGame = async () => (allGame.value = await get('game'))
const newGame = (_, createTime) => add('game', { ..._, path: _.parentPath, hide: 0, createTime: createTime || new Date().getTime() })
const setGameSize = async (_) => {
  try {
    let { mtimeMs } = fs.statSync(`${_.path}\\${_.name}`)
    if (mtimeMs !== _.mtimeMs || !_.size) {
      _.size = getSizeByDir(`${_.path}\\${_.name}`)
      await set('game', { ..._, mtimeMs })
    }
  } catch (e) {
    e.message.includes('no such file or directory') && del('game', _.id)
  }
}

const addGame = (list) =>
  new Promise((resolve) => {
    const arr = getAllGamesDir(list)
    num = arr.length
    let createTime = new Date().getTime()
    arr.map((_) =>
      newGame(_, createTime)
        .then((res) => get('game', res).then(setGameSize))
        .finally(() => num--)
    )
    let timer = setInterval(() => num === 0 && (clearInterval(timer), resolve()), 100)
  })

const getAllGamesDir = (pathList) => {
  return pathList.reduce((pre, _) => [...pre, ...fs.readdirSync(_.path, { withFileTypes: true }).filter((e) => e.isDirectory())], [])
}

const scanGames = async () => {
  let list = await get('library')
  await addGame(list)
  libraryList.value = list
  await getAllGame()
  allGame.value.map(setGameSize)
}

const updateGames = async () => {
  let list = await get('library')
  const newPath = differenceBy(list, [...libraryList.value], 'path')
  const deletePath = differenceBy([...libraryList.value], list, 'path')
  libraryList.value = list
  await addGame(newPath)
  deletePath.map((cur) => getList('game', (_) => _.path == cur.path && del('game', _.id)))
  ;(newPath.length || deletePath.length) && setTimeout(getAllGame, deletePath.length ? 500 : 0)
}

onMounted(async () => {
  get('app').then((res) => (apps.value = res))
  scanGames()
})

const cardClick = (index = active.value) => {
  const i = active.value == index ? -1 : index
  if (i !== -1) {
    const dom = document.getElementsByClassName('game-card')[i]
    dom.style.zIndex = ++zIndex.value
    setTimeout(() => dom.scrollIntoView({ behavior: 'smooth', block: 'center' }), 300)
    active.value = i
    let temp = gameList.value[i]
    let list = fs.readdirSync(`${temp.path}\\${temp.name}`, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith('.exe'))
    data.value = { ...temp, exeOptions: uniqBy([...(temp.exeOptions || []), ...list], 'name') }
  } else {
    setTimeout(() => {
      const temp = data.value
      isEqual(temp, gameList.value[index]) ||
        request('getFileIcon', temp.exe ? `${temp.exe.path}\\${temp.exe.name}` : '').then((res) => {
          set('game', { ...temp, img: temp.exe ? res.toDataURL() : '' }).then(getAllGame)
        })
      active.value = i
      data.value = {}
    }, 100)
  }
}

const selectExe = (e) => {
  request('selectExe', `${e.path}\\${e.name}`).then(([path] = []) => {
    if (!path) return
    const index = path.lastIndexOf('\\')
    const temp = { name: path.substring(index + 1), path: path.substring(0, index) }
    const exe = data.value.exeOptions.find((_) => `${_.path}\\${_.name}` == path)
    exe || data.value.exeOptions.push(temp)
    data.value.exe = exe || temp
  })
}

const selectApp = () => {
  request('selectExe', '').then(([path] = []) => {
    if (!path) return
    const index = path.lastIndexOf('\\')
    const temp = { name: path.substring(index + 1), path: path.substring(0, index) }
    request('getFileIcon', path).then((res) => {
      add('app', { ...temp, img: res.toDataURL() }).then(get('app').then((res) => (apps.value = res)))
    })
  })
}

const deleteApp = (e) => {
  del('app', e.id).then(get('app').then((res) => (apps.value = res)))
}

const launchGame = (game) => {
  openPath(game.exe)
  setTimeout(() => {
    window.api.useMain('minimize')
    set('game', { ...game, lastTime: new Date().getTime() }).then(getAllGame)
  }, 1000)
}

const openPath = (e) => {
  const path = typeof e == 'string' ? e : `${e.path}\\${e.name}`
  request('openPath', path)
}

const hideGame = (e) => {
  confirm.require({
    group: 'hide',
    message: `确认从列表中移除？`,
    accept: () => {
      set('game', { ...e, hide: 1 })
      if (active.value !== -1) active.value = -1
      getAllGame()
    }
  })
}

const uninstallGame = (e) => {
  confirm.require({
    group: 'uninstall',
    message: `请选择卸载程序或删除文件夹`,
    accept: () => {
      request('selectExe', `${e.path}\\${e.name}`).then(([path] = []) => path && openPath(path))
    }
  })
}

const deleteDir = () => {
  try {
    fs.rmSync(`${data.value.path}\\${data.value.name}`, { recursive: true })
    del('game', data.value.id)
    active.value = -1
    getAllGame()
  } catch (error) {
    toast.add({ severity: 'error', summary: '删除失败', detail: error.message, life: 3000 })
  }
}

const sortBtnClick = (e, item) => {
  const isActive = e.target.classList.value.includes('v-btn--active')
  isActive && (item.direction = !item.direction)
}

const addNewGame = async () => {
  const [path] = (await request('selectDir')) || []
  if (!path) return

  let temp = allGame.value.find((_) => `${_.path}\\${_.name}` == path)
  let id = temp?.id
  if (id) await set('game', { ...temp, createTime: new Date().getTime(), hide: 0 })
  else {
    const index = path.lastIndexOf('\\')
    temp = { name: path.substring(index + 1), parentPath: path.substring(0, index) }
    id = await newGame(temp)
  }
  temp = await get('game', id)
  await setGameSize(temp)
  getAllGame()
}

const selectSaveDir = () => {
  request('selectDir', { defaultPath: `${data.value.path}\\${data.value.name}` }).then(([path] = []) => {
    if (!path) return
    data.value.saveDir = path
    set('game', data.value)
  })
}
</script>

<template>
  <PageLayout minimize>
    <template #title-bar>
      <!-- 快捷应用栏 -->
      <SpeedDial :model="[...apps, { id: 'add' }]" nd direction="down" class="align-self-start mr-3" style="margin: 0.15rem 0 0 -0.5rem">
        <template #button="{ toggleCallback }">
          <v-btn size="small" icon="pi pi-box" flat @click="toggleCallback"></v-btn>
        </template>
        <template #item="{ toggleCallback, item }">
          <v-btn height="40" icon="" width="40" @click="toggleCallback">
            <i v-if="item.id == 'add'" style="width: 40px; height: 40px" class="pi pi-plus d-flex justify-center align-center" @click="selectApp" />
            <v-avatar v-else size="40" :image="item.img" class="elevation-3" @click="openPath(item)" @click.right="deleteApp(item)"></v-avatar>
          </v-btn>
        </template>
      </SpeedDial>

      <action-btn severity="secondary" @click="scanGames">扫 描</action-btn>

      <v-btn-toggle v-model="sort" mandatory shaped rounded="xl" nd class="mr-3 sort" variant="outlined">
        <v-btn v-for="item in sortOptions" :key="item.name" :value="item" @click="(e) => sortBtnClick(e, item)">
          {{ item.name }}<v-icon end style="font-size: 14px; margin-left: 4px"> pi pi-sort-amount-{{ item.direction ? 'down-alt' : 'down' }}</v-icon>
        </v-btn>
      </v-btn-toggle>
      <v-text-field v-model="keyword" prepend-inner-icon="pi pi-search" variant="outlined" density="compact" clearable hide-details nd class="search"></v-text-field>
    </template>
    <ConfirmDialog :draggable="false">
      <template #message="{ message }">
        <h2>{{ message.message }}</h2>
      </template>
    </ConfirmDialog>
    <Toast position="top-center" />
    <v-row no-gutters>
      <div id="appMask" :style="{ zIndex }" />
      <v-col cols="12" sm="2" class="pa-6" xxl="1">
        <v-responsive :aspect-ratio="1">
          <v-btn class="d-flex justify-center align-center w-100 h-100 rounded-lg border-sm" @click="addNewGame"><i class="pi pi-plus" style="font-size: 60px"></i></v-btn>
        </v-responsive>
      </v-col>
      <v-col v-for="(n, i) in gameList" :key="i" cols="12" sm="2" class="pa-6" xxl="1">
        <v-responsive :aspect-ratio="1 / 1.2" class="rounded-lg position-relative overflow-visible">
          <v-hover v-slot="{ isHovering, props }" :close-delay="0">
            <div :class="active == i ? ['show', 'elevation-16'] : []" class="game-card rounded-lg position-absolute w-100 top-0 left-0 overflow-hidden" v-bind="props">
              <!-- 右上角 X 按钮 -->
              <div class="close" :show="isHovering || active == i" @click="hideGame(n)">+</div>

              <v-responsive :aspect-ratio="1" style="background: #151515">
                <!-- 封面图 -->
                <img v-if="n.exe && n.img" :src="n.img" width="100%" height="100%" style="vertical-align: middle" />

                <div class="position-absolute w-100 h-100 top-0 left-0 d-flex justify-center align-center mask" :show="isHovering && active !== i && !!n.exe">
                  <!-- play 区域 -->
                  <v-scale-transition v-if="n.exe">
                    <div
                      v-show="isHovering && active !== i"
                      style="display: flex"
                      class="justify-center align-center w-75 h-75 rounded-circle cursor-pointer"
                      @click="launchGame(n)"
                    >
                      <!-- play 按钮 -->
                      <i class="pi pi-play-circle" style="font-size: 80px"></i>
                    </div>
                  </v-scale-transition>
                  <!-- 文件夹图标 -->
                  <i v-else class="pi pi-folder-open" style="font-size: 80px" />
                </div>
              </v-responsive>

              <!-- 名称 -->
              <h2 class="game-title text-truncate px-2 w-100" style="pointer-events: none">
                {{ n.showName || n.name }}
              </h2>

              <div class="w-100 rounded-b-lg overflow-hidden" style="background: #212121">
                <!-- 下拉区域 -->
                <v-expand-transition>
                  <div v-if="active == i">
                    <v-window v-model="extraInfo" :show-arrows="false">
                      <v-window-item class="px-2" :value="0">
                        <ButtonGroup class="d-flex w-100 mt-2">
                          <small-btn label="删除卸载" severity="danger" @click="uninstallGame(n)" />
                          <small-btn label="打开目录" severity="contrast" @click="openPath(n)" />
                          <small-btn class="return" severity="info" @click="extraInfo = 1">信 息<i class="pi pi-caret-right" /></small-btn>
                        </ButtonGroup>
                        <FloatLabel class="field my-5">
                          <label for="showName">自定义名称</label>
                          <InputText id="showName" v-model="data.showName" size="small" class="w-100" maxlength="8" />
                        </FloatLabel>
                        <FloatLabel class="field my-5 active">
                          <label>启动文件</label>
                          <v-btn variant="tonal" class="exe-add" @click="selectExe(n)">添加其他文件</v-btn>
                          <Listbox
                            v-model="data.exe"
                            class="list-box"
                            option-label="name"
                            :options="data.exeOptions"
                            empty-message="无"
                            scroll-height="3.82rem"
                            :focus-on-hover="false"
                          >
                            <template #option="{ option }">
                              <div :title="option.name" class="w-100 text-truncate">{{ option.name }}</div>
                            </template>
                          </Listbox>
                        </FloatLabel>
                        <FloatLabel class="field mt-5 active">
                          <label>存档位置</label>
                          <v-btn-group density="compact" class="save-dir-btn" divided>
                            <v-btn variant="tonal" @click="selectSaveDir">选择位置</v-btn>
                            <v-btn variant="tonal" @click="openPath(data.saveDir)">打开目录</v-btn>
                          </v-btn-group>
                          <Textarea v-model="data.saveDir" :rows="2" class="save-dir w-100 pa-1" disabled />
                        </FloatLabel>
                      </v-window-item>

                      <v-window-item class="px-2" :value="1">
                        <small-btn icon="pi pi-caret-left" class="pl-2 pr-3 mt-2 return" label="返 回" severity="info" @click="extraInfo = 0" />
                        <FloatLabel class="field mt-6 active">
                          <label>添加时间</label>
                          <Tag severity="success" class="info" :value="moment(data.createTime).format('YYYY-MM-DD HH:mm:ss')"></Tag>
                        </FloatLabel>
                        <FloatLabel class="field my-6 active">
                          <label>最后打开时间</label>
                          <Tag severity="info" class="info" :value="data.lastTime ? moment(data.lastTime).format('YYYY-MM-DD  HH:mm:ss') : '从未打开'"></Tag>
                        </FloatLabel>
                        <FloatLabel class="field my-6 active">
                          <label>最后修改时间</label>
                          <Tag severity="warn" class="info" :value="moment(data.mtimeMs).format('YYYY-MM-DD  HH:mm:ss')"></Tag>
                        </FloatLabel>
                        <FloatLabel class="field mb-2 active">
                          <label>游戏大小</label>
                          <Tag :value="`${(data.size / 1024 / 1024 / 1024).toFixed(2)} GB`" class="info"></Tag>
                        </FloatLabel>
                      </v-window-item>
                    </v-window>
                  </div>
                </v-expand-transition>

                <!-- 展开按钮 -->
                <v-scroll-y-transition>
                  <v-btn v-show="isHovering || active == i" variant="text" class="w-100 h4 rounded-0" size="small" :active="active == i" @click="cardClick(i)">
                    <i :class="['pi', active == i ? 'pi-angle-double-up' : 'pi-angle-double-down']"></i>
                  </v-btn>
                </v-scroll-y-transition>
              </div>
            </div>
          </v-hover>
        </v-responsive>
      </v-col>
    </v-row>

    <BottomDrawer :show-games="showGames" @update-games="updateGames"></BottomDrawer>
    <Confirm v-slot="{ action }" group="hide">
      <Button label="确认" size="small" severity="danger" @click="action"></Button>
    </Confirm>
    <Confirm v-slot="{ action, cancel }" group="uninstall">
      <Button label="卸载" size="small" severity="danger" @click="action"></Button>
      <span @click="cancel"><Button label="删除" size="small" severity="warn" @click="deleteDir"></Button></span>
    </Confirm>
  </PageLayout>
</template>

<style scoped lang="css">
#appMask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity 0.2s;
  pointer-events: none;
}

.sort {
  height: auto;
  > button {
    padding: calc(0.2rem - 0.66667px) 0.8rem;
    font-size: 14px;
    line-height: 1.5;

    & :deep(.v-btn__content) {
      pointer-events: none;
    }
  }
}

.search {
  flex: none;
  width: 240px;
  -webkit-app-region: no-drag;
  &:deep(.v-field__input) {
    padding: 0.2rem;
    font-size: 14px;
    line-height: 1.5;
    min-height: 0;
  }
  &:deep(.v-field) {
    padding-left: 0.5rem;
    border: 0.66667px solid rgba(255, 255, 255, 0.12);
    border-radius: 50px;
    transition: all 0.2s;
  }
  & :deep(.v-field__outline) {
    display: none;
  }
  &:deep(.v-field--active) {
    border-color: rgba(255, 255, 255, 0.4);
  }
}

.game-title {
  background: linear-gradient(0deg, #212121 1rem, transparent 100%);
  margin-top: -4rem;
  height: 5rem;
  line-height: 7rem;
  position: relative;
  text-align: center;
}

.game-card {
  transform-origin: center 30%;
  transform: scale(1);
  transition: transform 0.2s;

  &.show {
    transform: scale(1.3);
  }

  .close {
    position: absolute;
    top: -1rem;
    right: -2rem;
    width: 3rem;
    text-align: center;
    background: red;
    z-index: 1;
    height: 1rem;
    line-height: 1rem;
    transform: rotate(45deg) scale(1.5);
    transition: all 0.2s 0.2s;
    cursor: pointer;
    &[show='true'] {
      top: 0rem;
      right: -1rem;
    }
  }
  .mask {
    background: transparent;
    transition: all 0.2s;

    &[show='true'] {
      background: rgba(0, 0, 0, 0.3);
      backdrop-filter: blur(3px);
    }
  }
}

.field {
  &:has(*:focus),
  &:has(*.p-filled),
  &.active {
    label {
      top: -0.3rem;
      left: 0;
      font-size: 0.6rem;
      color: var(--p-floatlabel-focus-color);
    }
  }
}

.list-box {
  font-size: 0.8rem;
  width: 100%;

  & :deep(.p-listbox-list-container) {
    height: 3.82rem;
  }
  & :deep(li svg) {
    transform: scale(0.7);
    margin: 0;
  }
  & :deep(li) {
    padding: 0.2rem 0.4rem;
  }

  & :deep(.p-listbox-list-container)::-webkit-scrollbar {
    width: 0.4rem;
  }
  & :deep(.p-listbox-list-container)::-webkit-scrollbar-button {
    height: 2px;
  }
  & :deep(.p-listbox-list-container)::-webkit-scrollbar-thumb {
    background-color: #212121;
    border-radius: 8px;
  }
}

.exe-add {
  position: absolute;
  top: -1rem;
  right: 0;
  width: 5rem;
  height: 0.9rem;
  border-radius: 0.2rem;
  font-size: 0.6rem;
}

.save-dir-btn {
  position: absolute;
  top: -1rem;
  right: 0;
  height: 0.9rem;
  border-radius: 0.2rem;
  > button {
    min-width: 3.6rem;
    max-width: 3.6rem;
    font-size: 0.6rem;
  }
}

.save-dir {
  font-size: 0.6rem;
  resize: none;
  background: rgb(9, 9, 11) !important;
}

.info {
  line-height: 14px;
  font-size: 12px;
  /* color: #fff; */
}
</style>
